package com.example.user.service.bootstrap.security.controller;

import com.example.user.adapter.in.web.dto.*;
import com.example.user.adapter.out.persistence.entity.UserEntity;
import com.example.user.adapter.out.persistence.service.CustomUserDetailsService;
import com.example.user.service.application.service.TokenBlacklistService;
import com.example.user.service.common.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

/**
 * 用户控制器 - Spring Security JWT认证系统的核心控制器
 *
 * 这个控制器是整个认证系统的入口点，负责处理所有与用户认证和授权相关的HTTP请求
 * 主要功能包括：
 * 1. 用户登录认证 - 验证用户名密码，生成JWT令牌
 * 2. 用户注册 - 创建新用户账户，密码加密存储
 * 3. 用户信息获取 - 基于JWT令牌获取当前登录用户信息
 * 4. 用户登出 - 使JWT令牌失效（可选实现）
 * 5. 系统测试接口 - 用于开发阶段的功能验证
 *
 * 设计理念：
 * - RESTful API设计：使用标准的HTTP方法和状态码
 * - 统一响应格式：所有接口都返回Result<T>格式的响应
 * - 安全性优先：密码加密、JWT令牌、异常信息脱敏
 * - 日志记录：详细记录用户操作和系统异常
 *
 * 技术栈说明：
 * @RestController - Spring MVC注解，将类标记为REST控制器，自动将返回值序列化为JSON
 * @RequestMapping("/api/auth") - 设置控制器的基础路径，所有方法的路径都会以/api/auth开头
 * @RequiredArgsConstructor - Lombok注解，为所有final字段自动生成构造函数，实现依赖注入
 * @Slf4j - Lombok注解，自动生成名为log的日志对象，用于记录系统日志
 * @Tag - Swagger注解，用于API文档生成，将相关接口分组显示
 *
 * 依赖注入说明：
 * - JwtUtil：JWT令牌的生成和验证工具
 * - CustomUserDetailsService：用户详情服务，处理用户认证和数据操作
 * - UserMapper：用户数据访问层，直接操作数据库
 *
 * 安全考虑：
 * - 所有用户输入都经过验证（@Valid注解）
 * - 密码使用BCrypt加密存储
 * - 异常信息不暴露敏感数据
 * - JWT令牌有过期时间限制
 */
@Slf4j
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Tag(name = "用户认证和管理", description = "用户登录、注册、信息管理等接口")
public class UserController {

    /**
     * 自定义用户详情服务
     * 用于处理用户认证、查询用户信息等业务逻辑
     */
    private final CustomUserDetailsService userDetailsService;

    /**
     * JWT工具类
     * 用于生成和验证JWT Token
     */
    private final JwtUtil jwtUtil;

    /**
     * Token黑名单服务
     * 用于管理已失效的JWT Token，实现真正的登出功能
     */
    private final TokenBlacklistService tokenBlacklistService;

    /**
     * 用户登录接口 - JWT认证系统的核心入口
     *
     * 这是整个认证系统最重要的接口，负责验证用户身份并生成访问令牌
     *
     * 登录流程详解：
     * 1. 接收前端传来的用户名和密码（JSON格式）
     * 2. 使用@Valid注解自动验证请求参数的格式（如用户名长度、密码复杂度等）
     * 3. 调用CustomUserDetailsService.authenticate()方法验证用户名密码
     *    - 从数据库查询用户信息
     *    - 使用BCrypt算法验证密码哈希
     *    - 检查用户状态（是否被禁用）
     * 4. 验证成功后，使用JwtUtil生成JWT访问令牌
     *    - 令牌中包含用户ID、用户名、角色等信息
     *    - 设置令牌过期时间（通常24小时）
     * 5. 构造登录响应对象，包含令牌和基本用户信息
     * 6. 记录登录日志，便于安全审计
     *
     * 安全特性：
     * - 密码传输：前端应使用HTTPS确保密码传输安全
     * - 密码存储：数据库中存储的是BCrypt加密后的哈希值，不是明文
     * - 令牌安全：JWT令牌使用HMAC SHA256算法签名，防止篡改
     * - 失败处理：登录失败不暴露具体原因（用户不存在 vs 密码错误）
     *
     * 错误处理：
     * - 参数验证失败：返回400 Bad Request
     * - 用户名或密码错误：返回统一的"用户名或密码错误"消息
     * - 系统异常：返回通用错误消息，具体错误记录在日志中
     *
     * @param loginRequest 登录请求对象，包含用户名和密码
     *                    - username: 用户名，必填，长度3-20字符
     *                    - password: 密码，必填，长度6-20字符
     * @return Result<LoginResponse> 登录结果响应
     *         成功时包含：
     *         - accessToken: JWT访问令牌
     *         - tokenType: 令牌类型（固定为"Bearer"）
     *         - expiresIn: 令牌过期时间（秒）
     *         - userInfo: 用户基本信息（ID、用户名、角色等）
     * @PostMapping("/login") POST请求映射，处理/api/auth/login路径的POST请求
     * @Operation Swagger注解，描述该接口的作用和详细信息
     */
    @PostMapping("/login")
    @Operation(summary = "用户登录", description = "用户名密码登录，返回JWT token")
    public Result<LoginResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
        try {
            log.info("用户登录请求: {}", loginRequest.getUsername());

            // 调用用户详情服务验证用户凭据
            // authenticate方法会检查用户名是否存在，密码是否正确
            UserEntity user = userDetailsService.authenticate(loginRequest.getUsername(), loginRequest.getPassword());
            if (user == null) {
                // 如果用户认证失败，返回错误信息
                return Result.error("登录失败：用户名或密码错误");
            }

            // 用户认证成功，生成JWT Token
            // JWT Token是一种无状态的认证方式，包含用户信息且经过签名验证
            String token = jwtUtil.generateToken(user.getUsername());

            // 构建用户信息对象 - 为了避免暴露敏感信息，这里只返回部分信息
            // 使用Builder模式构建对象，代码更清晰易读
            UserInfo userInfo = UserInfo.builder()
                    .id(user.getId())                    // 用户ID
                    .username(user.getUsername())        // 用户名
                    .email(user.getEmail())              // 邮箱
                    .phone(user.getPhone())              // 手机号
                    .realName(user.getRealName())        // 真实姓名
                    .status(user.getStatus())            // 用户状态
                    .role(user.getRole())                // 用户角色
                    .createTime(user.getCreateTime())    // 创建时间
                    .lastLoginTime(LocalDateTime.now())  // 最后登录时间
                    .build();

            // 构建登录响应对象
            LoginResponse loginResponse = LoginResponse.builder()
                    .accessToken(token)                  // JWT访问令牌
                    .tokenType("Bearer")                 // 令牌类型
                    .userInfo(userInfo)                  // 用户信息
                    .expiresIn(System.currentTimeMillis() + 86400000L) // 24小时后过期
                    .build();

            log.info("用户登录成功: {}", user.getUsername());
            // 返回登录成功的响应结果
            return Result.success("登录成功", loginResponse);

        } catch (Exception e) {
            // 捕获异常并记录错误日志
            log.error("用户登录失败: {}", e.getMessage());
            // 返回登录失败的响应结果
            return Result.error("登录失败: " + e.getMessage());
        }
    }

    /**
     * 用户注册
     *
     * 用户注册接口，处理新用户的注册请求
     * 主要流程：
     * 1. 验证请求参数的有效性（通过@Valid注解自动验证）
     * 2. 检查用户名是否已经存在
     * 3. 验证两次输入的密码是否一致
     * 4. 对密码进行加密处理
     * 5. 创建新用户并保存到数据库
     *
     * @param registerRequest 注册请求对象，包含用户名、密码、邮箱等信息
     * @return Result<String> 注册结果响应
     */
    @PostMapping("/register")
    @Operation(summary = "用户注册", description = "用户注册接口")
    public Result<String> register(@Valid @RequestBody RegisterRequest registerRequest) {
        try {
            log.info("用户注册请求: username={}, email={}", registerRequest.getUsername(), registerRequest.getEmail());

            // 1. 检查用户名是否已存在
            // 通过CustomUserDetailsService查询数据库，避免重复用户名
            UserEntity existingUser = userDetailsService.getUserByUsername(registerRequest.getUsername());
            if (existingUser != null) {
                log.warn("注册失败: 用户名已存在 - {}", registerRequest.getUsername());
                return Result.error("用户名已存在，请选择其他用户名");
            }

            // 2. 验证两次密码是否一致
            // 前端应该已经验证过，但后端也需要再次验证确保数据安全
            if (!registerRequest.getPassword().equals(registerRequest.getConfirmPassword())) {
                log.warn("注册失败: 两次密码不一致 - {}", registerRequest.getUsername());
                return Result.error("两次输入的密码不一致");
            }

            // 3. 创建新用户对象
            UserEntity newUser = new UserEntity();
            newUser.setUsername(registerRequest.getUsername());

            // 4. 对密码进行加密
            // 使用Spring Security提供的密码编码器进行加密
            // 这里使用BCrypt算法，每次加密结果都不同，但验证时能正确匹配
            String encodedPassword = userDetailsService.generatePasswordHash(registerRequest.getPassword());
            newUser.setPassword(encodedPassword);

            // 5. 设置其他用户信息
            newUser.setEmail(registerRequest.getEmail());
            newUser.setPhone(registerRequest.getPhone());
            newUser.setRealName(registerRequest.getRealName());
            newUser.setRole("USER");  // 默认角色为普通用户
            newUser.setStatus(1);     // 默认状态为启用

            // 6. 保存用户到数据库
            // 使用MyBatis Plus的insert方法保存用户
            int result = userDetailsService.saveUser(newUser);

            if (result > 0) {
                log.info("用户注册成功: username={}, userId={}", registerRequest.getUsername(), newUser.getId());
                return Result.success("注册成功，请使用用户名和密码登录");
            } else {
                log.error("用户注册失败: 数据库插入失败 - {}", registerRequest.getUsername());
                return Result.error("注册失败，请稍后重试");
            }

        } catch (Exception e) {
            // 捕获所有异常，避免敏感信息泄露
            log.error("用户注册异常: username={}, error={}", registerRequest.getUsername(), e.getMessage(), e);
            return Result.error("系统异常，请稍后重试");
        }
    }

    /**
     * 获取当前用户信息接口 - 基于JWT令牌的用户信息查询
     *
     * 这个接口展示了JWT认证系统中如何获取当前登录用户的信息
     * 它是一个受保护的接口，只有携带有效JWT令牌的请求才能访问
     *
     * 工作流程详解：
     * 1. 客户端发送请求时，必须在HTTP头中携带JWT令牌
     *    格式：Authorization: Bearer <JWT_TOKEN>
     * 2. Spring Security的JwtAuthenticationFilter会拦截请求
     *    - 提取并验证JWT令牌的有效性
     *    - 解析令牌中的用户信息（用户名、角色等）
     *    - 创建Authentication对象并设置到SecurityContext中
     * 3. 控制器方法通过Authentication参数获取当前用户信息
     *    - Authentication.getName()获取用户名
     *    - Authentication.getAuthorities()获取用户权限
     * 4. 根据用户名从数据库查询完整的用户信息
     * 5. 返回用户详细信息（密码等敏感信息已过滤）
     *
     * 安全机制：
     * - JWT令牌验证：确保令牌未被篡改且未过期
     * - 用户状态检查：确保用户账户仍然有效（未被禁用）
     * - 敏感信息过滤：返回的用户信息不包含密码等敏感数据
     * - 权限控制：可以根据用户角色返回不同级别的信息
     *
     * 使用场景：
     * - 用户个人中心页面加载用户信息
     * - 前端验证用户登录状态
     * - 获取用户权限信息用于前端菜单控制
     * - 用户信息更新前的数据回显
     *
     * 错误处理：
     * - 令牌无效或过期：返回401 Unauthorized
     * - 用户不存在：返回404 Not Found
     * - 用户被禁用：返回403 Forbidden
     * - 系统异常：返回500 Internal Server Error
     *
     * @return Result<UserInfo> 用户信息响应
     *         成功时包含用户的详细信息：
     *         - id: 用户ID
     *         - username: 用户名
     *         - email: 邮箱
     *         - realName: 真实姓名
     *         - role: 用户角色
     *         - status: 账户状态
     *         - createTime: 创建时间
     *         注意：密码字段不会返回，确保安全性
     * @GetMapping("/info") GET请求映射，处理/api/auth/info路径的GET请求
     */
    @GetMapping("/info")
    @Operation(summary = "获取用户信息", description = "获取当前登录用户的详细信息")
    public Result<UserInfo> getUserInfo() {
        try {
            // 从Spring Security上下文中获取当前认证信息
            // SecurityContextHolder是Spring Security提供的安全上下文持有者
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            // 检查用户是否已认证
            if (authentication == null || !authentication.isAuthenticated()) {
                // 如果用户未认证，返回未授权错误
                return Result.unauthorized("用户未登录");
            }

            // 从认证信息中获取用户名
            String username = authentication.getName();
            // 根据用户名查询用户详细信息
            UserEntity user = userDetailsService.getUserByUsername(username);

            if (user == null) {
                // 如果用户不存在，返回错误信息
                return Result.error("用户不存在");
            }

            // 构建用户信息对象
            UserInfo userInfo = UserInfo.builder()
                    .id(user.getId())                    // 用户ID
                    .username(user.getUsername())        // 用户名
                    .email(user.getEmail())              // 邮箱
                    .phone(user.getPhone())              // 手机号
                    .realName(user.getRealName())        // 真实姓名
                    .status(user.getStatus())            // 用户状态
                    .role(user.getRole())                // 用户角色
                    .createTime(user.getCreateTime())    // 创建时间
                    .lastLoginTime(user.getLastLoginTime()) // 最后登录时间
                    .build();

            // 返回用户信息
            return Result.success(userInfo);

        } catch (Exception e) {
            // 捕获异常并记录错误日志
            log.error("获取用户信息失败: {}", e.getMessage());
            // 返回获取用户信息失败的响应结果
            return Result.error("获取用户信息失败: " + e.getMessage());
        }
    }

    /**
     * 用户登出接口 - JWT无状态认证的登出处理
     *
     * 这个接口展示了JWT认证系统中登出功能的实现方式
     * 由于JWT是无状态的令牌，服务端不存储会话信息，因此登出的处理方式与传统session不同
     *
     * JWT登出的特殊性：
     * 1. 无状态特性：JWT令牌是自包含的，服务端不存储令牌状态
     * 2. 令牌有效期：令牌在过期前始终有效，无法在服务端直接"销毁"
     * 3. 客户端责任：真正的登出主要依赖客户端删除存储的令牌
     *
     * 登出流程：
     * 1. 客户端发送登出请求（携带JWT令牌用于身份验证）
     * 2. 服务端验证令牌有效性（确保是合法的登出请求）
     * 3. 记录用户登出日志（用于安全审计和用户行为分析）
     * 4. 清理服务端相关状态（如缓存的用户信息、临时数据等）
     * 5. 返回登出成功响应
     * 6. 客户端收到响应后删除本地存储的JWT令牌
     *
     * 可选的增强安全措施：
     * 1. 令牌黑名单：将登出的令牌加入黑名单，拒绝后续使用
     *    - 优点：可以立即使令牌失效
     *    - 缺点：需要额外的存储空间和查询开销
     * 2. 短期令牌 + 刷新令牌：使用较短的访问令牌过期时间
     *    - 减少令牌被滥用的时间窗口
     *    - 通过刷新令牌机制保持用户体验
     * 3. 设备绑定：令牌与特定设备绑定，增加安全性
     *
     * 客户端配合工作：
     * 1. 删除本地存储的JWT令牌（localStorage、sessionStorage等）
     * 2. 清除相关的用户状态和缓存数据
     * 3. 重定向到登录页面或首页
     * 4. 停止所有需要认证的后台请求
     *
     * 安全考虑：
     * - 即使用户已登出，令牌在过期前仍可能被恶意使用
     * - 建议使用HTTPS防止令牌在传输过程中被截获
     * - 定期更换JWT签名密钥增强安全性
     * - 监控异常的令牌使用模式
     *
     * @return Result<String> 登出结果响应
     *         - 成功：返回"登出成功"消息
     *         - 失败：返回相应的错误信息
     * @PostMapping("/logout") POST请求映射，处理/api/auth/logout路径的POST请求
     */
    @PostMapping("/logout")
    @Operation(summary = "用户登出", description = "用户登出，清除认证信息并将token加入黑名单")
    public Result<String> logout(HttpServletRequest request) {
        try {
            // 从请求中提取JWT token
            String token = getJwtFromRequest(request);

            if (token != null) {
                // 将token加入黑名单，使其立即失效
                tokenBlacklistService.addToBlacklist(token);
                log.info("Token已加入黑名单: {}", token.substring(0, Math.min(token.length(), 20)) + "...");
            }

            // 清除Spring Security上下文中的认证信息
            // 这样用户下次访问需要认证的接口时就需要重新登录
            SecurityContextHolder.clearContext();
            log.info("用户登出成功");
            // 返回登出成功的响应结果
            return Result.success("登出成功，token已失效");
        } catch (Exception e) {
            // 捕获异常并记录错误日志
            log.error("用户登出失败: {}", e.getMessage());
            // 返回登出失败的响应结果
            return Result.error("登出失败: " + e.getMessage());
        }
    }

    /**
     * 测试接口 - JWT认证系统的功能验证接口
     *
     * 这是一个专门用于开发和测试阶段的接口，用来验证JWT认证系统是否正常工作
     * 它可以帮助开发者快速检查认证流程的各个环节
     *
     * 接口用途：
     * 1. 验证JWT令牌解析：确认服务端能正确解析客户端发送的JWT令牌
     * 2. 验证用户身份提取：确认能从令牌中正确提取用户信息
     * 3. 验证权限控制：确认只有有效令牌才能访问受保护的接口
     * 4. 验证过滤器链：确认JwtAuthenticationFilter正常工作
     * 5. 调试认证问题：当认证出现问题时，可以通过此接口定位问题
     *
     * 测试场景：
     * 1. 正常情况测试：
     *    - 使用有效的JWT令牌访问
     *    - 应该返回成功响应和用户信息
     * 2. 异常情况测试：
     *    - 不携带令牌：应该返回401 Unauthorized
     *    - 携带无效令牌：应该返回401 Unauthorized
     *    - 携带过期令牌：应该返回401 Unauthorized
     *    - 携带被篡改的令牌：应该返回401 Unauthorized
     *
     * 使用方法：
     * 1. 首先通过/api/auth/login接口获取JWT令牌
     * 2. 在请求头中添加：Authorization: Bearer <JWT_TOKEN>
     * 3. 发送GET请求到/api/auth/test
     * 4. 检查返回结果是否包含正确的用户信息
     *
     * 返回信息说明：
     * - 用户名：从JWT令牌中解析出的用户标识
     * - 权限列表：用户拥有的角色和权限信息
     * - 认证状态：确认用户已通过认证
     * - 当前时间：服务器处理请求的时间戳
     *
     * 生产环境注意事项：
     * - 此接口仅用于开发和测试，生产环境中应该移除或限制访问
     * - 可以通过配置文件控制是否启用此接口
     * - 建议添加访问频率限制，防止被恶意调用
     * - 不要在返回信息中包含敏感数据
     *
     * 调试技巧：
     * - 查看日志中的JWT解析过程
     * - 使用浏览器开发者工具检查请求头
     * - 使用在线JWT解码工具验证令牌内容
     * - 检查系统时间，确保令牌未过期
     *
     * @return Result<String> 测试结果响应
     *         成功时返回包含用户信息的测试消息：
     *         - 用户名
     *         - 权限列表
     *         - 当前时间
     *         - 认证状态确认
     * @GetMapping("/test") GET请求映射，处理/api/auth/test路径的GET请求
     */
    @GetMapping("/test")
    @Operation(summary = "测试接口", description = "需要JWT认证的测试接口")
    public Result<String> test() {
        // 从Spring Security上下文中获取当前认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 获取认证用户的用户名
        String username = authentication.getName();
        // 返回测试成功的响应结果，包含用户名
        return Result.success("Hello, " + username + "! JWT认证成功!");
    }

    /**
     * 从HTTP请求中提取JWT Token
     *
     * 这个私有方法用于从HTTP请求的Authorization头中提取JWT令牌
     * 支持标准的Bearer Token格式：Authorization: Bearer <token>
     *
     * 提取流程：
     * 1. 从请求头中获取Authorization字段的值
     * 2. 检查该值是否以"Bearer "开头（注意Bearer后面有一个空格）
     * 3. 如果格式正确，提取Bearer后面的令牌部分
     * 4. 返回纯净的JWT令牌字符串
     *
     * 安全考虑：
     * - 只接受标准的Bearer Token格式，拒绝其他格式
     * - 对提取的令牌进行基本的格式验证
     * - 不在日志中记录完整的令牌内容，避免泄露
     *
     * @param request HTTP请求对象
     * @return String JWT令牌字符串，如果未找到或格式不正确则返回null
     */
    private String getJwtFromRequest(HttpServletRequest request) {
        // 从请求头中获取Authorization字段
        String bearerToken = request.getHeader("Authorization");

        // 检查Authorization头是否存在且以"Bearer "开头
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            // 提取Bearer后面的令牌部分（去掉"Bearer "前缀）
            return bearerToken.substring(7);
        }

        // 如果没有找到有效的Bearer Token，返回null
        return null;
    }

}